package com.bbm.communitysystem.core.oss;

import cn.hutool.core.io.FileUtil;
import com.bbm.communitysystem.core.domain.FileStatus;
import com.bbm.communitysystem.core.redis.FileRedis;
import com.bbm.communitysystem.domain.OssFile;
import com.bbm.communitysystem.service.impl.OssFileServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 *
 * @apiNote oss系统
 * @author bbm
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class OSS {

    @Value("${file.url}")
    private String url;

    private final String clientUrl = "/oss/prod/";

    private final OssFileServiceImpl ossFileService;

    private final FileRedis redis;

    public List<OssFile> saveFile(String Subject, String user, MultipartFile ...file) {

        List<OssFile> ossFiles = new ArrayList<>();

        String path =  user + "/" + Subject + "/";
        String savePath = url + "/" + path;

        try {
            FileUtil.mkdir(savePath);
        } catch (Exception e) {
            log.error("创建文件夹失败");
            throw new RuntimeException(e);
        }

        for (MultipartFile multipartFile : file) {

            String fileName = System.currentTimeMillis() + "_" + Objects.requireNonNull(multipartFile.getOriginalFilename()).replaceAll(" ", "");

            String filePath = savePath + fileName.replaceAll(" ", "_");


            copyFile(multipartFile, filePath);


            OssFile ossFile = new OssFile();

            ossFile.setOriginFileName(multipartFile.getOriginalFilename());
            ossFile.setFileName(fileName);
            ossFile.setUser(user);
            ossFile.setSubject(Subject);
            ossFile.setClientUrl(clientUrl + path + fileName);
            ossFile.setServerPath(filePath);
            ossFile.setFileType(FileUtil.getType(new File(filePath)));
            try {
                ossFile.setHash(getMD5Checksum(filePath));
            } catch (IOException | NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            }
            ossFile.create(user);
            ossFiles.add(ossFile);


            Boolean save = save(ossFile);

            if (!save) {
                log.info("保存失败");
            }

        }


        return ossFiles;
    }

    public List<OssFile> saveFile (String Subject, String user, File ...file) {

        List<OssFile> ossFiles = new ArrayList<>();

        String path = user + "/" + Subject + "/";
        String savePath = url + "/" + path;

        try {
            FileUtil.mkdir(savePath);
        } catch (Exception e) {
            log.error("创建文件夹失败");
            throw new RuntimeException(e);
        }

        File type;
        for (File multipartFile : file) {

            String fileName = System.currentTimeMillis() + "_" + multipartFile.getName().replaceAll(" ", "");

            String filePath = savePath + fileName.replaceAll(" ", "_");

            type = FileUtil.copyFile(multipartFile, new File(filePath));

            OssFile ossFile = new OssFile();


            ossFile.setOriginFileName(multipartFile.getName());
            ossFile.setFileName(fileName);
            ossFile.setUser(user);
            ossFile.setSubject(Subject);
            ossFile.setClientUrl(clientUrl + path + fileName);
            ossFile.setServerPath(filePath);
            ossFile.setFileType(FileUtil.getType(type));
            try {
                ossFile.setHash(getMD5Checksum(filePath));
            } catch (IOException | NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            }
            ossFile.create(user);
            ossFiles.add(ossFile);

            Boolean save = save(ossFile);

            if (!save) {
                log.info("保存失败");
            }

        }


        return ossFiles;
    }

    /**
     * 保存默认生成头像
     * @param Subject 主题
     * @param user 用户
     * @param bytes 头像流
     */
    public List<OssFile> saveFile(String Subject, String user, byte[] ...bytes) {

        String path = user + "/" + Subject + "/";
        String savePath = url + "/" + path;
        List<OssFile> ossFiles = new ArrayList<>();
        for (byte[] flux : bytes) {
            OutputStream outputStream;
            String fileName = user + "_" + System.currentTimeMillis() + ".png";
            try {


                File mkdir = FileUtil.mkdir(savePath);

                outputStream = new FileOutputStream(savePath + fileName);
                outputStream.write(flux);
                outputStream.flush();
                outputStream.close();

                OssFile ossFile = new OssFile();
                ossFile.setOriginFileName(fileName);
                ossFile.setFileName(fileName);
                ossFile.setUser(user);
                ossFile.setHash(getMD5Checksum(savePath + fileName));
                ossFile.setSubject(Subject);
                ossFile.setServerPath(savePath + fileName);
                ossFile.setClientUrl(clientUrl + path + fileName);
                ossFile.setFileType("png");

                ossFile.create(user);

                ossFiles.add(ossFile);

                Boolean save = save(ossFile);

                if (!save) {
                    log.info("保存失败");
                }
            } catch (IOException | NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            }

        }

        return ossFiles;
    }

    public void deleteFile(String path) {
        log.info("删除文件:{}", path);
        try {
            File file = new File(path);
            if (file.exists()) {
                boolean delete = file.delete();
                if (!delete) {
                    log.error("文件删除失败");
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void copyFile(MultipartFile file, String path) {
        OutputStream outputStream;
        try {

            InputStream inputStream = file.getInputStream();
            outputStream = new FileOutputStream(path);


            outputStream.write(inputStream.readAllBytes());


            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }



    }

    private void copyFile(File file, String path) {
        try (InputStream inputStream = new FileInputStream(file);
             OutputStream outputStream = new FileOutputStream(path, true)){



            outputStream.write(inputStream.readAllBytes());

            outputStream.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private Boolean save(OssFile ossFile) {
        return ossFileService.save(ossFile);
    }

    @Transactional(rollbackFor = Exception.class)
    public int deleteFile(Integer ossFileId) {

        OssFile byId = ossFileService.getById(ossFileId);
        deleteFile(byId.getServerPath());
        ossFileService.removeById(ossFileId);

        return 0;
    }

    /**
     * 大文件上传
     * @param subject 主题
     * @param user  用户
     * @param fileHash  文件哈希
     * @param OriginFileName  文件名
     * @return Boolean
     *
     * @apiNote
     * 传输文件块时主题应为null，文件块哈希和文件哈希必须传。
     * 当文件块传输完毕时，主题必须传，文件块哈希和文件哈希必须传。
     *
     * 结束条件 主题有内容代表所有文件块已经传输完毕
     *
     */
    public OssFile saveBigFile(String subject, String user,
                               String fileHash, String OriginFileName) {

        String cachePath = user + "/cache/" + fileHash + "/";
        String cacheSavePath = url + "/" + cachePath;

        String path = user + "/" + subject + "/";
        String savePath = url + "/" + path;
        String fileName = OriginFileName.replaceAll(" ", "_");
        String filePath = savePath + fileName;
        String md5;

        FileUtil.mkdir(savePath);

        File cacheFileList = new File(cacheSavePath);

        File[] files = cacheFileList.listFiles();

        if (files == null) {
            return null;
        }

        OutputStream outputStream;
        try {
            outputStream = new FileOutputStream(filePath);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
        for (int i = 0; i < files.length; i++) {

            try (InputStream inputStream = new FileInputStream(new File(cacheSavePath + fileHash + "-" + i));) {
                outputStream.write(inputStream.readAllBytes());
                outputStream.flush();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        }

        try {
            outputStream.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }


        try {
            md5 = getMD5Checksum(filePath);

//            if (!md5.equals(fileHash)) {
//                return null;
//            }

        } catch (IOException | NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }

        File file = new File(filePath);


        OssFile ossFile = new OssFile();
        ossFile.setFileName(fileName);
        ossFile.setSubject(subject);
        ossFile.setFileType(FileUtil.getType(file));
        ossFile.setServerPath(filePath);
        ossFile.setClientUrl(clientUrl + path + fileName);
        ossFile.setOriginFileName(OriginFileName);
        ossFile.setHash(md5);
        ossFile.create(user);

        if (save(ossFile)) {
            FileUtil.del(cacheSavePath);

            redis.removeFileChunk(fileHash);

            return ossFile;
        }

        return null;
    }

    /**
     * 文件块上传
     * @param file 文件
     * @param fileHash 文件哈希
     * @param userId 用户id
     * @param index 文件块索引
     * @return Boolean
     *
     * @apiNote 上传成功返回true，失败返回false，配合saveBigFile使用，根据文件哈希对应
     *
     */
    public Boolean saveCheckFile(MultipartFile file, String fileHash, String userId, String index) {

        String path = userId + "/cache/" + fileHash + "/";
        String savePath = url + "/" + path;

        String filePath = savePath + fileHash + "-" + index;

        FileUtil.mkdir(savePath);

        copyFile(file, filePath);

        int i = redis.fileChunkAdd(fileHash);

        if (i == 1) {
            redis.fileChunkCreate(fileHash);
        }

        return true;

    }


    public FileStatus stopUploadAndDel(String fileHash, String userId) {

        String path = userId + "/cache/" + fileHash + "/";
        String savePath = url + "/" + path;

        FileStatus fileStatus = new FileStatus();
        redis.removeFileChunk(fileHash);

        boolean del = FileUtil.del(savePath);

        if (del) {
            fileStatus.setUrl("删除成功");
            fileStatus.setHash("");
            fileStatus.setCurrentChunk("");
            fileStatus.setNextChunk("");
        }

        return fileStatus;
    }



    /**
     * 计算文件的MD5校验码
     *
     * @param filePath 文件路径
     * @return MD5校验码
     * @throws IOException              如果发生I/O错误
     * @throws NoSuchAlgorithmException 如果找不到MD5算法
     */
    public static String getMD5Checksum(String filePath) throws IOException, NoSuchAlgorithmException {
                 BigInteger bi = null;
        try {
            byte[] buffer = new byte[8192];
            int len = 0;
            MessageDigest md = MessageDigest.getInstance("MD5");
            File f = new File(filePath);
            FileInputStream fis = new FileInputStream(f);
            while ((len = fis.read(buffer)) != -1) {
                        md.update(buffer, 0, len);
                     }
            fis.close();
            byte[] b = md.digest();
            bi = new BigInteger(1, b);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bi.toString(16);
    }

    public FileStatus getUploadStatus(String videoHash) {
        FileStatus fileStatus = new FileStatus();
        Integer fileChunk = redis.getFileChunk(videoHash);

        if (fileChunk == null) {
            return fileStatus;
        }

        fileStatus.setNextChunk((fileChunk + 1) + "");
        fileStatus.setHash(videoHash);
        fileStatus.setCurrentChunk(fileChunk + "");

        return fileStatus;
    }



}
